Skip to content

feat(desktop): manage Runtime Host update policy - #3771

Merged
M4n5ter merged 5 commits into
mainfrom
feat/desktop-runtime-host-update-policy
Aug 25, 2026
Merged

feat(desktop): manage Runtime Host update policy#3771
M4n5ter merged 5 commits into
mainfrom
feat/desktop-runtime-host-update-policy

Conversation

@M4n5ter

@M4n5ter M4n5ter commented Aug 25, 2026

Copy link
Copy Markdown
Member
English

Summary

Managed Runtime Hosts can now follow a per-Host update policy: manual, an exact version, the latest stable channel, or the next preview channel. Desktop reads and changes the policy through the Host's bound SSH operator, exposes one-shot reconciliation, and reconnects after a compatible update.

Automatic policies are offered only when the Host reports a healthy update scheduler. Every Desktop policy and reconciliation request is fenced to the expected managed service target, so a stale or redirected operator cannot mutate another Host.

Local Desktop development now prepares and caches a private CLI archive on first use. MAKA_RUNTIME_HOST_SETUP_ARCHIVE remains an optional override; normal development no longer depends on a matching npm version being published.

Fixes #3768

Verification

  • Desktop renderer and Storybook typechecks — passed
  • Desktop, CLI, and Runtime Host affected suites — passed
  • Biome lint, formatting, repository typecheck, and git diff --check — passed
  • Runtime Host full suite — one unrelated shared-cache race failed; the exact test passed in isolation
  • Exercised a real Desktop-to-Linux Host flow over SSH without a setup-package environment variable: lazily build and upload the companion CLI, reconnect across a compatibility upgrade, save manual/latest policies, run an immediate check, and restore the manual policy

Screenshots

Automatic update policy Compact action menu

AI use

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: OpenAI Codex contributed implementation, tests, analysis, and remote Desktop verification under maintainer direction and review

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No
简体中文

摘要

现在可以为每个受管 Runtime Host 分别选择更新策略:手动、固定版本、Latest 稳定频道或 Next 预览频道。Desktop 通过该 Host 绑定的 SSH operator 读取和修改策略,支持立即执行一次 reconciliation,并在兼容更新后重新连接。

只有 Host 报告更新调度器状态正常时才开放自动更新策略。Desktop 发出的每个策略与 reconciliation 请求都会绑定预期的受管服务目标,避免过期或被重定向的 operator 修改其他 Host。

本地 Desktop 开发现在会在首次需要时自动生成并缓存私有 CLI archive。MAKA_RUNTIME_HOST_SETUP_ARCHIVE 仍可用于显式覆盖;常规开发不再依赖 npm 已发布相同版本。

修复 #3768

验证

  • Desktop renderer 与 Storybook 类型检查 — 通过
  • Desktop、CLI 与 Runtime Host 受影响测试套件 — 通过
  • Biome lint、格式检查、仓库类型检查与 git diff --check — 通过
  • Runtime Host 完整测试套件 — 一个与本 PR 无关的共享缓存竞争失败;该测试单独重跑通过
  • 已在未设置安装包环境变量的情况下,通过 SSH 对真实 Linux Host 验证完整 Desktop 流程:懒构建并上传配套 CLI、跨兼容版本升级后重连、保存手动/Latest 策略、立即检查并恢复手动策略

截图

自动更新策略 紧凑操作菜单

AI 使用

  • 没有生成式工具作出实质性贡献
  • 生成式工具作出了实质性贡献

工具与范围:OpenAI Codex 在维护者指导和审核下参与了实现、测试、分析和远程 Desktop 验证

检查清单

  • 测试覆盖本次变更,并会在缺少该变更时失败
  • lint、格式检查、类型检查和受影响测试均在本地通过

本 PR 是否会改变行为?

  • 是 — 已在上方摘要中说明

@M4n5ter
M4n5ter force-pushed the feat/desktop-runtime-host-update-policy branch 2 times, most recently from e69de0f to ae5a1dd Compare August 25, 2026 07:50
@M4n5ter
M4n5ter changed the base branch from feat/runtime-host-update-scheduler to main August 25, 2026 07:50
Expose each managed Host's update policy and one-shot reconciliation through its bound SSH operator. Require explicit scheduler support before presenting automatic updates as available.

Generated-by: Codex
@M4n5ter
M4n5ter force-pushed the feat/desktop-runtime-host-update-policy branch from ae5a1dd to d61bc15 Compare August 25, 2026 08:18
Exclude stale build output from development CLI archives, own the packaging child lifecycle, and keep managed update targeting and scheduler support represented once.

Generated-by: Codex
@M4n5ter
M4n5ter marked this pull request as ready for review August 25, 2026 08:55
Apply reconciliation service snapshots atomically, avoid redundant SSH reads after failed status, and fail closed when policy state becomes uncertain.

Generated-by: Codex

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this head and found a blocking issue.

[P2] Development build closing race allows immediate retry to join a doomed build

runtime-host-setup-package.ts:104-111 reuses shared developmentBuild even while its close is still in flight. After the last waiter aborts and closes, a new resolve() can immediately pick the same closing build and receive stopped instead of starting a fresh build.

Fix: mark closing builds as unavailable and handle last-waiter/new-waiter race with proper ownership.

Checks on ca70545a12 are package: success but test: pending — not green.

简体中文存在开发构建关闭竞态阻断。

Terminate interactive SSH process trees through the shared platform abstraction.\n\nBuild transient CLI archives outside formal release outputs and clean them with the Desktop resolver.\n\nGenerated-by: OpenAI Codex
Keep closing development builds as serialization barriers without letting new callers join their doomed result.\n\nFence process-tree termination with the PTY exit identity before acting on its PID.\n\nGenerated-by: OpenAI Codex

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update on 6cafd6fac7:

[P2] Closing build still reusable

runtime-host-setup-package.ts:103-111 reuses developmentBuild while its close is still in flight; immediate retry joins the doomed build and gets stopped.

[P2] SSH terminator lacks incarnation guard

runtime-host-ssh-terminal.ts:997-1002 passes PID to async terminator without hasExited/incarnation check; after exit and before discovery, a reused PID could receive SIGTERM/SIGKILL intended for the old PTY.

Checks on 6cafd6fac7 are package: success but test: queued — not green.

简体中文存在构建关闭复用与 SSH 误杀两项阻断。

@M4n5ter

M4n5ter commented Aug 25, 2026

Copy link
Copy Markdown
Member Author

@Astro-Han

English

Thank you for the review. Both findings were valid on 6cafd6fac and are fixed in the current head f2ca7082c:

  • A new setup/update now treats a closing development build as a serialization barrier. It waits for that build to close, then starts a fresh build instead of joining the doomed result.
  • The PTY records synchronous exit acknowledgement and passes both hasExited and beforeSignal to the shared process-tree terminator before it acts on the PID.

The focused race tests pass, together with the repository build, typecheck, lint, format checks, and Desktop/UI knip checks. The package CI check has passed; the test check is still running.

简体中文

感谢审查。这两项问题在 6cafd6fac 上均成立,已在当前 head f2ca7082c 中修复:

  • 新的 setup/update 现在会把正在关闭的开发构建视为串行化屏障:等待其关闭完成后启动新构建,不再加入注定失败的旧结果
  • PTY 会同步记录退出事实,并在共享进程树终止器按 PID 操作前传入 hasExitedbeforeSignal 两项防护

相关竞态测试已经通过,全仓 build、typecheck、lint、format 以及 Desktop/UI knip 检查也均通过。package CI 已通过,test 检查仍在运行

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update on f2ca7082c6:

[P2] SSH terminator still lacks PID incarnation binding

runtime-host-ssh-terminal.ts:1000-1013 checks hasExited boolean before discovery, but after readPosixProcesses()/taskkill the numeric pid is reused without verifying the original incarnation. The terminator can SIGTERM a reused PID's process group.

Previous closing-build race is now fixed.

Checks on f2ca7082c6 are test: queued — code is NO-GO.

简体中文存在进程代次绑定缺失。

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update on f2ca7082c6:

[P2] SSH terminator PID reuse window still open

ssh-terminal.ts:1000-1013 now passes hasExited/beforeSignal, but readPosixProcesses() is an async PS snapshot. If the PTY exits and its PID is reused before the JavaScript exit event (hasExited still false), the subsequent kill(-pid) can target an unrelated process group. Previous build stopped race is fixed; this incarnation gap remains.

Fix: bind to pidfd or /proc/<pid>/stat start-time and re-verify incarnation before each signal.

Checks on f2ca7082c6 are test+package: success.

简体中文存在进程代次复用导致的误杀风险。

@M4n5ter

M4n5ter commented Aug 25, 2026

Copy link
Copy Markdown
Member Author

@Astro-Han

English

Thank you for following up. The remaining window is theoretically real, but after examining the operating-system boundary we do not consider it an actionable P2 blocker for this PR.

The current implementation checks hasExited after asynchronous topology discovery and checks beforeSignal immediately before its first OS action. A wrong target would therefore require all of the following: the original process has already exited in the kernel, the node-pty exit callback has not yet run, the OS has already reused the numeric PID, and the replacement process has become a process-group leader with the same PGID. If any descendant still belongs to the original process group, that PGID remains the group we own and cannot yet be reused.

There is no portable atomic process-incarnation binding for this operation across macOS, Linux, and Windows. ChildProcess.kill() and IPty.kill() ultimately have the same numeric-PID limitation. Adding a process start-time check would still leave a check-to-signal TOCTOU window and would create false assurance; closing it fully would require platform-specific process handles or pidfd support, while process-group cleanup would remain a separate concern.

For that reason, we are retaining the current bounded checks rather than adding non-portable complexity that does not actually close the race. The concrete closing-build race is fixed, and both current CI checks are now green.

简体中文

感谢继续审查。这里描述的残余窗口在理论上存在,但在检查操作系统边界后,我们认为它不是本 PR 中可合理处理的 P2 阻断问题。

当前实现在异步进程拓扑发现后检查 hasExited,并在首次执行操作系统动作前立即检查 beforeSignal。要误伤其他进程,必须同时满足:原进程已在内核中退出、node-pty 的退出回调尚未执行、操作系统已经复用了该数字 PID,并且新进程恰好成为具有相同 PGID 的进程组 leader。只要原进程组仍有任何后代存在,该 PGID 仍属于我们拥有的进程组,也不能被复用。

macOS、Linux 和 Windows 之间不存在可移植的原子进程代次绑定机制;ChildProcess.kill()IPty.kill() 最终也具有相同的数字 PID 限制。增加进程启动时间校验仍会留下校验到发信号之间的 TOCTOU 窗口,只会形成虚假的安全保证。要完全闭合,需要平台专用的 process handle 或 pidfd,而进程组清理仍然需要单独处理。

因此,我们保留当前有边界的防护,不增加无法真正闭合竞态的非可移植复杂度。实际存在的 development build 关闭竞态已经修复,当前两项 CI 检查也均已通过。

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up on f2ca7082c6:

Author's reply clarifies the remaining PID-reuse window requires process exit + exit-callback delay + PID reuse + PGID reuse simultaneously, with no portable atomic incarnation binding across platforms. Adding start-time checks would still leave TOCTOU and false assurance, and would need platform-specific handles.

Therefore downgrading this from P2 to P3 (non-blocking observation). The concrete closing-build race remains fixed.

No remaining P0-P1.

简体中文该窗口按现有可移植边界降为 P3 观察。

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APPROVE — f2ca708, no remaining P0-P1 after downgrade per author portable-boundary reply, test+package success.

@M4n5ter
M4n5ter merged commit 38d29e9 into main Aug 25, 2026
2 checks passed
@M4n5ter
M4n5ter deleted the feat/desktop-runtime-host-update-policy branch August 25, 2026 12:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(desktop): manage per-Host Runtime Host update policy

2 participants